import * as html2canvas from 'html2canvas';
import { Rect } from '../geometry/rect';
import { HorizontalAlign, VerticalAlign } from './enums';
import { Size } from '../geometry/size';

/**
 * html文本呈现器
 */
export class HtmlTextRender {
    //#region 字段定义
    /**要呈现的html内容 */
    private _html: string;
    /**加载html错误信息 */
    private _error: any;
    /**呈现html内容的画布 */
    private _htmlCanvas: HTMLCanvasElement | null;
    /**html呈现的区域 */
    private _htmlRect: Rect = new Rect();
    /**底图或html内容水平对齐方式 */
    private  _horizontalAlign = HorizontalAlign.Left;
    /**底图或html内容垂直对齐方式 */
    private _verticalAlign = VerticalAlign.Top;
    /**获取渲染图片的可见高宽度 */
    private _getRenderBounds: () => Rect;
    /**
     * 获取上次呈现内容时的区域大小
     */
    private _lastRenderLimitSize: Size = Size.Empty;
    //#endregion

    //#region 构造函数
    /**
     * 构造函数
     * @param getRenderBounds 
     * @param horizontalAlign
     * @param verticalAlign
     */
    constructor(getRenderBounds: () => Rect, horizontalAlign: HorizontalAlign, verticalAlign: VerticalAlign) {
        this._getRenderBounds = getRenderBounds;
        this._horizontalAlign = horizontalAlign;
        this._verticalAlign = verticalAlign;
    }
    //#endregion

    //#region 属性定义
    /**
     * 获取或设置呈现的html文本
     */
    public get html() {
        return this._html;
    }
    public set html(value: string) {
        this._html = value;
    }
    /**
     * 获取错误信息
     */
    public get error() {
        return this._error;
    }
    /**获取或设置底图或html内容水平对齐方式 */
    public get horizontalAlign() {
        return this._horizontalAlign;
    }
    public set horizontalAlign(value: HorizontalAlign) {
        if (value !== this._horizontalAlign) {
            this._horizontalAlign = value;
        }   
    }
    /**获取或设置底图或html内容垂直对齐方式 */
    public get verticalAlign() {
        return this._verticalAlign;
    }
    public set verticalAlign(value: VerticalAlign) {
        if (value !== this._verticalAlign) {
            this._verticalAlign = value;
        }   
    }
    /**
     * 获取呈现html的canvas
     */
    public get htmlCanvas() {
        return this._htmlCanvas;
    }
    /**
     * 获取或设置html呈现范围
     */
    public get bounds() {
        return this._htmlRect;
    }
    public set bounds(value: Rect) {
        this._htmlRect = value;
    }
    //#endregion

    //#region 公共方法
    /**
     * 
     * @param ctx 渲染html内容到书写上下文
     */
    public render(ctx: CanvasRenderingContext2D) {
        if (ctx) {
            if (this._htmlCanvas && !this.bounds.isEmpty()) {
                const bounds = this.bounds;
                const htmlCanvas = this._htmlCanvas;
                ctx.drawImage(htmlCanvas, bounds.x, bounds.y, bounds.width, bounds.height);
            }
        }
    }
    /**存在加载成功的html内容 */
    public get existHtml() {
        return this._htmlCanvas !== null;
    }
    /**
     * 异步加载html内容
     * @param callback 回调处理
     */
    public loadHtmlAsync(callback?: (error: any, canvas: HTMLCanvasElement | null, rect: Rect | null) => void) {
        const _callback = (error: any, canvas: HTMLCanvasElement | null = null, rect: Rect | null = null) => {
            this._htmlCanvas = canvas;
            this._htmlRect = rect || Rect.Empty;
            this._error = error;
            if (callback) {
                callback(error, canvas, rect);
            }
        };
        const html = this.html;
        if (html && html.trim() !== '') {
            const renderBounds = this._getRenderBounds ? this._getRenderBounds() : Rect.Empty;
            this._lastRenderLimitSize = new Size(renderBounds.width, renderBounds.height);
            // 创建一个临时容器来承载html内容
            const container = document.createElement('div');
            container.innerHTML = html;
            container.style.display = 'hidden';
            container.style.position = 'absolute';
            container.style.left = '9999px';
            container.style.top = '9999px';
            container.style.width = `${renderBounds.width}px`;
            document.body.appendChild(container);
            const finishAction = (error: any, canvas?: HTMLCanvasElement) => {
                // 移除临时添加的html标签
                document.body.removeChild(container);
                let _canvas: HTMLCanvasElement | null = null, rect: Rect | null = null;
                if (!error && canvas && (canvas.width * canvas.height) > 0) {
                    _canvas = canvas;
                    rect = this.setHtmlRenderBounds(canvas.width, canvas.height);
                }
                // 回调处理
                _callback(error, _canvas, rect);
            };
            // html转换为canvas
            const converter: any = html2canvas;
            converter(container, { backgroundColor: null, allowTaint: true })
                .then((canvas: HTMLCanvasElement) => finishAction(null, canvas))
                .catch((error: any) => finishAction(error));
        } else {
            // 回调处理
            _callback(null, null, null);
        }
    }
    /**
     * 根据当前有效呈现区域来重新刷新html呈现区域
     */
    public refreshHtmlRenderBounds(callback?: (bounds: Rect) => void) {
        const _callback = (bounds: Rect) => {
            if (callback) {
                callback(bounds);
            }
        };
        const renderBounds = this._getRenderBounds ? this._getRenderBounds() : Rect.Empty;
        if (renderBounds.width !== this._lastRenderLimitSize.width || 
            renderBounds.height !== this._lastRenderLimitSize.height) {
                // 如果外部区域大小发生变化则重新生成html-canvas
                this.loadHtmlAsync((_error: any, _canvas: HTMLCanvasElement | null = null, _rect: Rect | null = null) => {
                    _callback(_rect || Rect.Empty);
                });
            } else {
                // 如果外部区域大小没发生变化则直接重新计算渲染范围
                if (this._htmlCanvas) {
                    this._htmlRect = this.setHtmlRenderBounds(this._htmlCanvas.width, this._htmlCanvas.height);
                } else {
                    this._htmlRect = Rect.Empty;
                }
                _callback(this._htmlRect);
            }
    }
    //#endregion

    //#region 私有方法
    /**
     * 设置html-canvas的渲染范围
     * @param htmlCanvasWidth 
     * @param htmlCanvasHeight 
     */
    private setHtmlRenderBounds(htmlCanvasWidth: number, htmlCanvasHeight: number) {
        let x = 0, y = 0, width = 0, height = 0;
        const renderLimitWidth = this._lastRenderLimitSize.width;
        const renderLimitHeight = this._lastRenderLimitSize.height;
        if (this.horizontalAlign === HorizontalAlign.Left) {
            x = 0;
        } else if (this.horizontalAlign === HorizontalAlign.Center) {
            x = (renderLimitWidth - htmlCanvasWidth) / 2;
        } else if (this.horizontalAlign === HorizontalAlign.Right) {
            x = renderLimitWidth - htmlCanvasWidth;
        }
        if (this.verticalAlign === VerticalAlign.Top) {
            y = 0;
        } else if (this.verticalAlign === VerticalAlign.Middle) {
            y = (renderLimitHeight - htmlCanvasHeight) / 2;
        } else if (this.verticalAlign === VerticalAlign.Bottom) {
            y = renderLimitHeight - htmlCanvasHeight;
        }
        // 不允许显示的起始坐标小于0
        if (x < 0) {
            x = 0;
        }
        if (x < 0) {
            y = 0;
        }
        width = htmlCanvasWidth;
        height = htmlCanvasHeight;
        return new Rect(x, y, width, height);
    }
    //#endregion
}